﻿using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using balticSD.WindowsMobile;
using balticSD.WindowsMobile.IO;
using Lime49.UI;
using LockCrypt.Core;
using LockCrypt.DAL;

namespace LockCrypt {
    /// <summary>
    /// Allows synchronizing with a mobile device.
    /// </summary>
    public partial class WinSynchronize : Window {
        private Style _styError,
                      _stySuccess, // every successful query
                      _styStatus; // intermediate status messages
        private WindowsMobileDeviceManager _man;
        private ILogProvider _logger;
        private bool _versionValid = false;

        /// <summary>
        /// Initializes a new <see cref="WinSynchronize"/>.
        /// </summary>
        public WinSynchronize() {
            InitializeComponent();
            _logger = new RichTextBoxLogger(txtProgress);
            txtLocalPath.Text = DalConfigurationManager.Provider.FormattedName;
            _styError = this.FindResource("ErrorStyle") as Style;
            _stySuccess = this.FindResource("SuccessStyle") as Style;
            _styStatus = this.FindResource("StatusStyle") as Style;

        }

        /// <summary>
        /// Initializes the WindowsMobileDeviceManager when the window loads.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            _logger.Log(I18nUtils.GetString("DeviceSync", "WaitingForConn") + "...", StatusMessage.StatusMessageType.Status);
            _man = new WindowsMobileDeviceManager();
            _man.DeviceConnected += new EventHandler<DeviceConnectionChangedEventArgs>(man_DeviceConnected);
            _man.DeviceDisconnected += (s, dce) => _logger.Log(I18nUtils.GetString("DeviceSync", "DeviceDisconnected"), StatusMessage.StatusMessageType.Status);
            if(_man.Devices.Count > 0) {
                man_DeviceConnected(null, new DeviceConnectionChangedEventArgs(_man.Devices[0]));
            }
        }

        /// <summary>
        /// Updates the database path from the device on connection.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="balticSD.WindowsMobile.DeviceConnectionChangedEventArgs"/> instance containing the event data.</param>
        private void man_DeviceConnected(object sender, DeviceConnectionChangedEventArgs e) {
            _logger.Log(I18nUtils.GetString("DeviceSync", "DeviceConnected"), StatusMessage.StatusMessageType.Success);
            string deviceDatabasePath = string.Empty,
                   deviceIconPath = string.Empty;
            Version lcMobileVersion = new Version(1,0);
            WindowsMobileDevice device = e.Device;
            CERegistry registry = new CERegistry(device);
            CERegistryKey key = registry.DeviceRegistryCurrentUser.OpenSubkey("LockCrypt");
            if(key != null) { // try to read it from the registry
                try {
                    deviceDatabasePath = key.GetValue("DBLocation", string.Empty).ToString();
                    _logger.Log(I18nUtils.GetString("DeviceSync", "ReadDBPathDevice"), StatusMessage.StatusMessageType.Success);
                    deviceIconPath = key.GetValue("IconLocation", string.Empty).ToString();
                    _logger.Log(I18nUtils.GetString("DeviceSync", "ReadPathFromDeviceFmt", deviceIconPath), StatusMessage.StatusMessageType.Success);
                    lcMobileVersion = new Version(key.GetValue("LCMobileVersion", "1.0.0.0").ToString());
                } catch { }
            }
            if(deviceDatabasePath.Length == 0) { 
                // use the default path
                deviceDatabasePath = CEFileSystemInfo.GetSpecialFolder(CESpecialFolder.MyDocuments, device).FullName;
                _logger.Log(I18nUtils.GetString("DeviceSync", "UsingDefaultPath"), StatusMessage.StatusMessageType.Status);
            }
            if(deviceIconPath.Length == 0) {
                // use default icon path
                string deviceInstallDir = CEFileSystemInfo.GetSpecialFolder(CESpecialFolder.Programs, device).FullName;
                deviceIconPath = System.IO.Path.Combine(deviceInstallDir, "Icons");
                _logger.Log(I18nUtils.GetString("DeviceSync", "UsingDefaultIconPath"), StatusMessage.StatusMessageType.Status);
            }
            Version minVersion = new Version(1,6,1); // custom icons added
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate {
                txtDevicePath.Text = deviceDatabasePath;
                txtIconsDevice.Text = deviceIconPath;
                if(lcMobileVersion < minVersion) {
                    _logger.Log(I18nUtils.GetString("DeviceSync", "LCMobileVersionTooLow", minVersion.ToString(3)), StatusMessage.StatusMessageType.Error);
                    _versionValid = false;
                } else {
                    _versionValid = true;
                }
            });
        }

        /// <summary>
        /// Determines whether a device is connected.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsDeviceConnected(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = this.IsLoaded && _man.Devices.Count > 0 && _versionValid;
        }

        /// <summary>
        /// Determines which files to copy depending on the button which was clicke and performs synchronization.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Synchronize(object sender, ExecutedRoutedEventArgs e) {
            if(_man.Devices.Count == 0) {
                return;
            } else if(txtDevicePath.Text.Trim().Length == 0) {
                DialogBox.ShowAlert(this, I18nUtils.GetString("DeviceSync", "DBPathDeviceRequired"), I18nUtils.GetString("Errors", "Error"));
            }
            bool includeIcons = chkIncludeIcons.IsChecked == true;
            string deviceDbPath = txtDevicePath.Text,
                   deviceIconsPath = txtIconsDevice.Text;
            WindowsMobileDevice device = _man.Devices[0];
//            BackgroundWorker worker = new BackgroundWorker();
  //          worker.DoWork += (s, dwe) => {
                try {
                    IDbProvider localProvider = DalConfigurationManager.Provider;
                    XmlProvider remoteProvider = null; // only xml supported by lockcrypt mobile
                    if(!localProvider.CanAttemptConnection) {
                        throw new LockCryptException(I18nUtils.GetString("DeviceSync", "LocalFileDoesNotExist"), 232);
                    }
                    CEFileInfo remoteInfo = new CEFileInfo(device, deviceDbPath);
                    string localIconsDirectoryPath = System.IO.Path.Combine(Utils.GetApplicationPath(), "Icons");
                    CEDirectoryInfo remoteIconsDirectory = new CEDirectoryInfo(device, deviceIconsPath);
                    if(e.OriginalSource == btnSync) {
                        if(remoteInfo.Exists) {
                            string tempRemoteFilename = CopyToLocal(remoteInfo.FullName);
                            remoteProvider = new XmlProvider(tempRemoteFilename, new Aes256EncryptionService()); // only xml supported by lockcrypt mobile
                            if(!remoteProvider.CheckPassword()) {
                                WinPromptPassword dlgPromptPass = new WinPromptPassword(remoteProvider) { Owner = this };
                                if(dlgPromptPass.ShowDialog() != true) {
                                    return;
                                }
                                remoteProvider = (XmlProvider)dlgPromptPass.DatabaseProvider;
                            }

                            DatabaseContents localContents = localProvider.ReadDatabase(),
                                             remoteContents = remoteProvider.ReadDatabase();
                            _logger.Log(I18nUtils.GetString("DeviceSync", "ReadDatabase"), StatusMessage.StatusMessageType.Success);
                            DatabaseContents mergedContents = DatabaseContents.SynchronizeDatabases(localContents, remoteContents);

                            localProvider.ClearDatabase();
                            localProvider.ImportDatabase(mergedContents, _logger);
                            _logger.Log(I18nUtils.GetString("DeviceSync", "MergedLocalDb"), StatusMessage.StatusMessageType.Success);
                            remoteProvider.ClearDatabase();
                            remoteProvider.ImportDatabase(mergedContents, _logger);
                            _logger.Log(I18nUtils.GetString("DeviceSync", "MergedRemoteDb"), StatusMessage.StatusMessageType.Success);

                            FileInfo tempRemoteInfo = new FileInfo(tempRemoteFilename);
                            CopyLocalToDevice(remoteProvider, remoteInfo);
                            tempRemoteInfo.Delete();
                        } else {
                            CopyLocalToDevice(localProvider, remoteInfo);
                            _logger.Log(I18nUtils.GetString("DeviceSync", "CopiedLocalToDeviceFmt", ((FileBasedDatabase)localProvider).FileName, remoteInfo.FullName), StatusMessage.StatusMessageType.Success);
                        }
                        if(includeIcons) {
                            SynchronizeIcons(device, localIconsDirectoryPath, remoteIconsDirectory);
                        }
                    } else if(e.OriginalSource == btnToDevice) {
                        CopyLocalToDevice(localProvider, remoteInfo);
                        _logger.Log(I18nUtils.GetString("DeviceSync", "CopiedLocalToDeviceFmt", localProvider.FormattedName, remoteInfo.FullName), StatusMessage.StatusMessageType.Success);
                        if(includeIcons) {
                            CopyLocalIconsToDevice(device, localIconsDirectoryPath, remoteIconsDirectory);
                        }
                    } else if(e.OriginalSource == btnFromDevice) {
                        if(!remoteInfo.Exists) {
                            throw new LockCryptException(I18nUtils.GetString("DeviceSync", "FileOnDeviceDoesNotExist"), 232);
                        }
                        string tempRemoteFilename = CopyToLocal(deviceDbPath); // only xml supported by lockcrypt mobile
                        remoteProvider = new XmlProvider(tempRemoteFilename, new Aes256EncryptionService());
                        if(!remoteProvider.CheckPassword()) {
                            WinPromptPassword dlgPromptPass = new WinPromptPassword(remoteProvider) { Owner = this };
                            if(!dlgPromptPass.ShowDialog() == true) {
                                return;
                            }
                            remoteProvider = (XmlProvider)dlgPromptPass.DatabaseProvider;
                        }
                        // merge to local to keep encryption/password settings
                        DatabaseContents remoteContents = remoteProvider.ReadDatabase();
                        _logger.Log(I18nUtils.GetString("DeviceSync", "ReadDatabase"), StatusMessage.StatusMessageType.Success);
                        localProvider.ClearDatabase();
                        localProvider.ImportDatabase(remoteContents, _logger);
                        _logger.Log(I18nUtils.GetString("DeviceSync", "CopiedDeviceToLocalFmt", remoteProvider.FileName, localProvider.FormattedName), StatusMessage.StatusMessageType.Success);
                        File.Delete(tempRemoteFilename);
                        if(includeIcons) {
                            CopyRemoteIconsToLocal(localIconsDirectoryPath, remoteIconsDirectory);
                        }
                    }
                } catch(LockCryptException lEx) {
                    _logger.Log(I18nUtils.GetString("Errors", "ErrorCodeFmt", lEx.ErrorCode.ToString(), lEx.Message), StatusMessage.StatusMessageType.Error);
                } catch(Exception ex) {
                    _logger.Log(I18nUtils.GetString("Errors", "Error") + ": " + ex.Message, StatusMessage.StatusMessageType.Error);
                }
            /*};
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                }
            };
            worker.RunWorkerAsync();*/
        }

        /// <summary>
        /// Copies a remote file to a local temporary file.
        /// </summary>
        /// <param name="remotePath">The remote path.</param>
        /// <returns>The filename of the temporary file which was copied.</returns>
        private string CopyToLocal(string remotePath) {
            FileInfo localInfo = new FileInfo(System.IO.Path.GetTempFileName());
            CEFileInfo remoteInfo = new CEFileInfo(_man.Devices[0], remotePath);
            FileStream localStream = null;
            CEFileStream remoteStream = null;
            try {
                localStream = localInfo.Open(FileMode.Open, FileAccess.ReadWrite); // temp copy of DB copied from device
                remoteStream = remoteInfo.Open(CEFileAccess.Read, CEFileMode.OpenExisting, CEFileShare.None);
                byte[] buffer = new byte[2048];
                int read = 0;
                while((read = remoteStream.Read(buffer, 0, buffer.Length)) > 0) {
                    localStream.Write(buffer, 0, read);
                }
                _logger.Log(I18nUtils.GetString("DeviceSync", "CopiedRemoteDbTempFmt", localInfo.FullName), StatusMessage.StatusMessageType.Status);
            } finally {
                if(localStream != null) {
                    try {
                        localStream.Close();
                    } catch {
                    }
                }
                if(remoteStream != null) {
                    try {
                        remoteStream.Close();
                    } catch {
                    }
                }
            }
            return localInfo.FullName;
        }

        /// <summary>
        /// Copies a local file to the device, using the same password for the new file as the local database
        /// </summary>
        /// <param name="localProvider">The local database provider.</param>
        /// <param name="remoteInfo">The remote path info.</param>
        private void CopyLocalToDevice(IDbProvider localProvider, CEFileInfo remoteInfo) {
            XmlProvider deviceCompatibleProvider = new XmlProvider(System.IO.Path.GetTempFileName(), new Aes256EncryptionService(localProvider.Encryption.EncryptionKey));
            DatabaseContents localContents = localProvider.ReadDatabase();
            _logger.Log(I18nUtils.GetString("DeviceSync", "ReadDatabase"), StatusMessage.StatusMessageType.Success);
            deviceCompatibleProvider.ImportDatabase(localContents, _logger);
            FileInfo localInfo = new FileInfo((deviceCompatibleProvider).FileName);

            FileStream localDb = null;
            CEFileStream remoteDb = null;
            try {
                localDb = File.OpenRead(localInfo.FullName);
                remoteDb = remoteInfo.Create(true); // overwrite if it exists
                remoteDb.Write(localDb);
            } finally {
                if(localDb != null) {
                    try {
                        localDb.Close();
                    } catch {
                    }
                }
                if(remoteDb != null) {
                    try {
                        remoteDb.Close();
                    } catch {
                    }
                }
            }
            File.Delete((deviceCompatibleProvider).FileName);
        }

        #region Icons
        /// <summary>
        /// Synchronizes all icons from both the local and remote icon directories.
        /// </summary>
        /// <param name="device">The device from which to copy the icons.</param>
        /// <param name="localDirectory">The local icon directory.</param>
        /// <param name="remoteDirectory">The remote icon directory.</param>
        private void SynchronizeIcons(WindowsMobileDevice device, string localDirectory, CEDirectoryInfo remoteDirectory) {
            CopyRemoteIconsToLocal(localDirectory, remoteDirectory);
            CopyLocalIconsToDevice(device, localDirectory, remoteDirectory);
        }

        /// <summary>
        /// Copies icons from a device to the local icons directory.
        /// </summary>
        /// <param name="localDirectory">The local source directory.</param>
        /// <param name="remoteDirectory">The remote directory from which to copy the icons.</param>
        private void CopyRemoteIconsToLocal(string localDirectory, CEDirectoryInfo remoteDirectory) {
            string[] localNames = Directory.GetFiles(localDirectory, "*.png");
            var remoteIcons = remoteDirectory.GetFiles("*.png");
            foreach(CEFileInfo remoteIconInfo in remoteIcons) {
                try {
                    if(!localNames.Contains(remoteIconInfo.Name)) {
                        using(CEFileStream remoteIcon = remoteIconInfo.Open(CEFileAccess.Read, CEFileMode.OpenExisting, CEFileShare.None)) {
                            FileInfo localIconInfo = new FileInfo(System.IO.Path.Combine(localDirectory, remoteIconInfo.Name));
                            using(FileStream localIcon = localIconInfo.Create()) {
                                // local filestream
                                _logger.Log(I18nUtils.GetString("DeviceSync", "CopiedFromDeviceFmt", remoteIconInfo.Name, localIconInfo.FullName), StatusMessage.StatusMessageType.Success);
                                byte[] buffer = new byte[2048];
                                int read = 0;
                                while((read = remoteIcon.Read(buffer, 0, buffer.Length)) > 0) {
                                    localIcon.Write(buffer, 0, read);
                                }
                                remoteIcon.Close();
                                localIcon.Close();
                            }
                        }
                    }
                } catch(Exception ex) {
                    _logger.Log(I18nUtils.GetString("Errors", "ErrorCodeFmt", "238", ex.Message), StatusMessage.StatusMessageType.Status);
                }
            }
        }

        /// <summary>
        /// Copies icons from the local icons directory to a device.
        /// </summary>
        /// <param name="device">The device onto which to copy the icons.</param>
        /// <param name="localDirectory">The local directory.</param>
        /// <param name="remoteDirectory">The remote directory.</param>
        private void CopyLocalIconsToDevice(WindowsMobileDevice device, string localDirectory, CEDirectoryInfo remoteDirectory) {
            string[] localNames = Directory.GetFiles(localDirectory, "*.png");
            string remoteIconDirectory = remoteDirectory.FullName;
            foreach(string localIconPath in localNames) {
                try {
                    string localIconName;
                    using(System.Drawing.Image imgIcon = System.Drawing.Image.FromFile(localIconPath)) {
                        if(imgIcon.Width > 32 && imgIcon.Height > 32) {
                            //localIconName = System.IO.Path.GetFileName(localIconPath);
                            //Console.WriteLine("Skipping {0}, size too large", localIconName);
                            continue;
                        }
                        imgIcon.Dispose();
                    }
                    localIconName = System.IO.Path.GetFileName(localIconPath);// no point doing this if the image is too large anyway
                    CEFileInfo remoteIconInfo = new CEFileInfo(device, System.IO.Path.Combine(remoteIconDirectory, localIconName));
                    if(!remoteIconInfo.Exists) {
                        using(CEFileStream remoteIcon = remoteIconInfo.Create(true)) {
                            using(FileStream localIcon = File.OpenRead(localIconPath)) {
                                _logger.Log(I18nUtils.GetString("DeviceSync", "CopiedLocalToDeviceFmt", localIcon.Name, remoteIconInfo.FullName), StatusMessage.StatusMessageType.Success);
                                remoteIcon.Write(localIcon);
                            }
                        }
                    }
                } catch(Exception ex) {
                    _logger.Log(I18nUtils.GetString("Errors", "ErrorCodeFmt", "237", ex.Message), StatusMessage.StatusMessageType.Status);
                }
            }
        }

        #endregion

        /// <summary>
        /// Waits for the worker to complete.
        /// </summary>
        /// <param name="worker">The worker.</param>
        private void WaitForWorker(BackgroundWorker worker) {
            while(worker.IsBusy) {
                Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
            }
        }
    }
}
